/*
*  Copyright (c) 2001 Sun Microsystems, Inc.  All rights
*  reserved.
*
*  Redistribution and use in source and binary forms, with or without
*  modification, are permitted provided that the following conditions
*  are met:
*
*  1. Redistributions of source code must retain the above copyright
*  notice, this list of conditions and the following disclaimer.
*
*  2. Redistributions in binary form must reproduce the above copyright
*  notice, this list of conditions and the following disclaimer in
*  the documentation and/or other materials provided with the
*  distribution.
*
*  3. The end-user documentation included with the redistribution,
*  if any, must include the following acknowledgment:
*  "This product includes software developed by the
*  Sun Microsystems, Inc. for Project JXTA."
*  Alternately, this acknowledgment may appear in the software itself,
*  if and wherever such third-party acknowledgments normally appear.
*
*  4. The names "Sun", "Sun Microsystems, Inc.", "JXTA" and "Project JXTA"
*  must not be used to endorse or promote products derived from this
*  software without prior written permission. For written
*  permission, please contact Project JXTA at http://www.jxta.org.
*
*  5. Products derived from this software may not be called "JXTA",
*  nor may "JXTA" appear in their name, without prior written
*  permission of Sun.
*
*  THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
*  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
*  OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
*  DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
*  ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
*  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
*  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
*  USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
*  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
*  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
*  OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
*  SUCH DAMAGE.
*  ====================================================================
*
*  This software consists of voluntary contributions made by many
*  individuals on behalf of Project JXTA.  For more
*  information on Project JXTA, please see
*  <http://www.jxta.org/>.
*
*  This license is based on the BSD license adopted by the Apache Foundation.
*
*  $Id: PipeSearch.java,v 1.9 2007/03/26 20:21:48 nano Exp $
*/

package net.jxta.myjxta.search;

import net.jxta.discovery.DiscoveryEvent;
import net.jxta.discovery.DiscoveryListener;
import net.jxta.discovery.DiscoveryService;
import net.jxta.logging.Logging;
import net.jxta.myjxta.MyJXTA;
import net.jxta.myjxta.dialog.Dialog;
import net.jxta.myjxta.dialog.OneToOneCommandDialog;
import net.jxta.myjxta.util.Group;
import net.jxta.myjxta.util.Peer;
import net.jxta.myjxta.util.objectmodel.PeerNode;
import net.jxta.protocol.DiscoveryResponseMsg;
import net.jxta.protocol.PipeAdvertisement;

import java.io.IOException;
import java.util.Enumeration;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * A class that implements a Search object for search of peers
 *
 * @author james todd [gonzo at jxta dot org]
 * @version $Id: PipeSearch.java,v 1.9 2007/03/26 20:21:48 nano Exp $
 */

public final class PipeSearch
        extends AbstractSearch
        implements DiscoveryListener {

    /**
     * The title for the current search
     */
    private static final String TITLE = "Peer Search";

    /**
     * A description of the current search
     */
    private static final String DESCRIPTION = "peers";
    private static final String PREFIX =
            OneToOneCommandDialog.IMFREE_COMMAND_NAME;
    private static final Logger LOG = Logger.getLogger(PipeSearch.class.getName());
    private Thread m_waitForConnectAndSearch;

    /**
     * Create a new Search object to search for peers
     *
     * @param group  the group in which this search is done
     * @param myJxta the MyJxta instance for system wide defaults.
     *               The currently selected group in this instance my be different
     *               from group
     */
    public PipeSearch(Group group, MyJXTA myJxta) {
        super(group, myJxta);

        setTitle(TITLE);
    }

    /**
     * Return a description of the current search
     *
     * @return a description of the current search
     */
    public String getDescription() {
        return getDescription(DESCRIPTION);
    }

    /**
     * Start the search with the indicated search term
     *
     * @param term the search term
     */
    public void search(String term) {
        setTerm(term);

        String criteria = (term.trim().length() == 0 ||
                term.trim().equals(WILDCARD) ? WILDCARD : term);

        cancel();
        isRunning(true);

        final DiscoveryService discover = getDiscovery();
        // Append the real beginning string if the user wants a begin with or
        // is query

//        criteria = Dialog.IMFREE_USER_NAME + Dialog.IMFREE_DELIMITER +
//            criteria;
        criteria = PREFIX + Dialog.IMFREE_DELIMITER + criteria;

        try {
            process(discover.getLocalAdvertisements(DiscoveryService.ADV,
                    PipeAdvertisement.NameTag, criteria));
        } catch (IOException ioe) {
            if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
                LOG.log(Level.FINE, "Exception durng advertisement lookup", ioe);
            }
        }


        final String criteria1 = criteria;


        m_waitForConnectAndSearch = new WaitForConnectAndSearchThread(discover, criteria1, this);

        m_waitForConnectAndSearch.start();
    }

    public void cancel() {
        if (isRunning()) {
            DiscoveryService discovery = getDiscovery();
            if (discovery != null) {
                discovery.removeDiscoveryListener(this);
            }
            isRunning(false);
            if (m_waitForConnectAndSearch != null && m_waitForConnectAndSearch.isAlive()) {
                synchronized (m_waitForConnectAndSearch) {
                    m_waitForConnectAndSearch.notify();
                }

            }
            super.cancel();
        }
    }

    public void discoveryEvent(DiscoveryEvent de) {
        DiscoveryResponseMsg rm = de.getResponse();
        if (rm.getDiscoveryType() == DiscoveryService.ADV) {
            process(rm.getAdvertisements());
        }
    }

    private void process(Enumeration advertisements) {
        PipeAdvertisement pa;
        Object o;
        String name;

        while (advertisements != null &&
                advertisements.hasMoreElements()) {
            o = advertisements.nextElement();

            if (o instanceof PipeAdvertisement) {
                pa = (PipeAdvertisement) o;
                name = pa.getName();

                if (name != null &&
                        name.startsWith(PREFIX)) {
//                    name.startsWith(Dialog.IMFREE_USER_NAME)) {
                    add(new PeerNode(new Peer(pa), group));
                }
            }
        }
    }

    private final class WaitForConnectAndSearchThread extends Thread {
        private final DiscoveryService m_discover;
        private final String m_criteria1;
        private boolean forceImmediately = false;
        private final PipeSearch searcher;

        public WaitForConnectAndSearchThread(DiscoveryService p_discover, String p_criteria1, PipeSearch searcher) {
            m_discover = p_discover;
            m_criteria1 = p_criteria1;
            setDaemon(true);
            this.searcher = searcher;
        }

        public synchronized void start() {
            if (group.isConnected()) {
                forceImmediately = true;
                run(); //if we are already connected dont start an extra thread (will save resouces)
                //instead start the search in the same thread
            } else {
                setName(this.getClass().getName() + " looking for: " + m_criteria1);
                super.start();    //not connected, wait for a connect and start the remove search afterwards
            }

        }

        /**
         * wait for a connect and then start a remote search
         */
        public void run() {
        try {
            if (!group.isConnected())
            { // if we are not connected do two searches... one immediatly (for ad-hoc mode/local lan peers), one after we are connected
                m_discover.getRemoteAdvertisements(null, DiscoveryService.ADV,
                        PipeAdvertisement.NameTag, m_criteria1, REMOTE_THRESHOLD, searcher);

                int waitCounter = 1000;  //1000*500ms = maximum thread endurance
                while (!group.isConnected() && isRunning() && waitCounter > 0 && !forceImmediately) {
                    try {
                        synchronized (this) {
                            wait(500);
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    waitCounter--; //make sure this loop also terminates if no connection can be archived at all
                }
            }



                m_discover.getRemoteAdvertisements(null, DiscoveryService.ADV,
                                PipeAdvertisement.NameTag, m_criteria1, REMOTE_THRESHOLD, searcher);
            } catch(Exception e){
                e.printStackTrace();
            }
            isRunning(false);
        }
    }
}


